package sortFile

import (
	"awesomeProject/sortBigDataFile/convUtil"
	"bufio"
	"io"
	"log"
	"os"
	"sort"
	"strconv"
	"sync"
)

const (
	cpus        = 8
	fileSplitor = "\n"
)

var (
	tmpFilePrefis        = "sortTmp"
	tmpFileSuffix        = ".txt"
	tmpDir string
	intsOff       int
	wg            sync.WaitGroup
	allFileName   sync.Map
	total         int
)

type intsContainer struct {
	ints []int
	ch   chan string
}

func splitAndSortFile(file *os.File, tmpDir string, lines int) (map[int]string, error) {
	tmpDir = tmpDir
	total = lines
	reader, err := initFM(file,tmpDir)
	if err != nil {
		return nil, err
	}
	//大文件分割成小文件并排序
	for i := 0; i < cpus; i++ {
		intC := &intsContainer{
			ints: make([]int, lines/cpus),
			ch:   make(chan string, 0),
		}
		tmpFile, err := os.OpenFile(tmpDir+tmpFilePrefis+strconv.Itoa(i)+tmpFileSuffix, os.O_CREATE|os.O_RDWR, 0644)
		if err != nil {
			log.Fatal("open File err ", tmpDir+tmpFilePrefis+strconv.Itoa(i)+tmpFileSuffix)
		}
		wg.Add(1)
		go injectTmpFile(intC, tmpFile, i)
		err = fullInts(reader, intC, i)
		if err != nil {
			return nil, err
		}
	}
	wg.Wait()
	fMap := make(map[int]string)
	allFileName.Range(func(key, value interface{}) bool {
		fMap[key.(int)] = value.(string)
		return true
	})
	return fMap, nil
}

func initFM(file *os.File ,tmpDir string) (*bufio.Reader, error) {
	reader := bufio.NewReader(file)
	err := os.Mkdir(tmpDir, 0644)
	if err != nil {
		if os.IsExist(err) {
		} else {
			log.Fatalf("make temp dir error %v",err)
		}
	}
	return reader, nil
}

func fullInts(reader *bufio.Reader, intC *intsContainer, batchIdx int) error {
	for i := 0; i < len(intC.ints); i++ {
		readString, err := reader.ReadString('\n')
		//读到最后一批，ints有余量
		if err == io.EOF {
			intsOff = i //inclusive
			intC.ints = intC.ints[:intsOff]
			break
		}
		if err != nil {
			log.Fatal("read line error int fullBatch ", err)
		}
		toInt, err := convUtil.StrToInt(readString)
		if err != nil {
			return err
		}
		intC.ints[i] = toInt
	}
	// 最后一批ints不够装
	if isLastBatch(batchIdx) {
		lefts := total % cpus
		for lefts > 0 {
			lefts--
			readString, err := reader.ReadString('\n')
			if err != nil {
				if err == io.EOF {
					break
				} else {
					log.Fatal("last batch read error", err)
				}
			}
			toInt, err := convUtil.StrToInt(readString)
			if err != nil {
				return err
			}
			intC.ints = append(intC.ints, toInt)
		}
	}
	sort.Ints(intC.ints)
	intC.ch <- "done"
	return nil
}

func injectTmpFile(intc *intsContainer, tmpFile *os.File, fileIdx int) {
	//wait for sort finished
	<-intc.ch
	defer tmpFile.Close()
	defer close(intc.ch)
	writer := bufio.NewWriter(tmpFile)
	for i := 0; i < len(intc.ints); i++ {
		_, err := writer.WriteString(strconv.Itoa(intc.ints[i]) + fileSplitor)
		if err != nil {
			log.Fatal("injdect into ", tmpDir+tmpFilePrefis+strconv.Itoa(fileIdx)+tmpFileSuffix, "err ")
		}
	}
	writer.Flush()
	allFileName.Store(fileIdx, tmpFile.Name())
	wg.Done()
}

func isLastBatch(batchIdx int) bool {
	return batchIdx == cpus-1
}
